#pragma once
#include <map>
#include <memory>
#include <queue>
#include <mutex>

enum class EPopStates {
	succ,
	failt,
	timeout
};

template <class VALUE>
class TsafeBlockQueue {
private:
	std::mutex _popMutex, _pushMutex, _statusMutex;
	std::condition_variable _notEmpty;
	std::condition_variable _notFull;
	size_t _capacity;
	std::queue<VALUE> _queueData;
	bool _closed = false;

	bool isEmpty() {
		std::lock_guard<std::mutex> lvLock(_statusMutex);
		return _queueData.empty();
	}

	bool isFull() {
		std::lock_guard<std::mutex> lvLock(_statusMutex);
		return _capacity > 0 && _queueData.size() >= _capacity;
	}
public:
	EPopStates pop(VALUE& result, uint32_t avTimeOutSec)
	{
		std::unique_lock<std::mutex> lvLock(_popMutex);
		while (isEmpty())
		{
			if (avTimeOutSec == 0)
			{
				_notEmpty.wait(lvLock);
			}
			else 
			{
				if (_notEmpty.wait_for(lvLock, std::chrono::seconds(avTimeOutSec)) == std::cv_status::timeout)
				{
					return EPopStates::timeout;
				}
			}
			
		}
		if (_closed)
		{
			return EPopStates::failt;
		}
		result = _queueData.front();
		_queueData.pop();
		_notFull.notify_one();
		return EPopStates::succ;
	}
	void push(VALUE&& avValue)
	{
		std::unique_lock<std::mutex> lvLock(_pushMutex);
		while (isFull())
		{
			_notFull.wait(lvLock);
		}
		if (_closed)
		{
			return;
		}
		_queueData.push(std::move(avValue));
		_notEmpty.notify_one();
	}
	size_t size()
	{
		std::lock_guard<std::mutex> lvLock(_statusMutex);
		return _queueData.size();
	}
	void clear()
	{
		std::lock_guard<std::mutex> lvLock(_statusMutex);
		std::queue<VALUE> emptyQueue;
		_queueData.swap(emptyQueue);
	}

	void setCapacity(uint32_t avCapacity) {
		_capacity = avCapacity;
	}

	void closeQueue() {
		std::lock_guard<std::mutex> lvLock(_statusMutex);
		_closed = true;
		_notFull.notify_all();
		_notEmpty.notify_all();
	}
};